Skip to content

Wheels: link FreeType against the bundled HarfBuzz - #9913

Open
akx wants to merge 1 commit into
python-pillow:mainfrom
akx:harfing
Open

Wheels: link FreeType against the bundled HarfBuzz#9913
akx wants to merge 1 commit into
python-pillow:mainfrom
akx:harfing

Conversation

@akx

@akx akx commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Follows up on #8497.

This came up as a side effect of #9909, where the last commit needed to increase the allowed comparison epsilon for the Arabic-language test, since different platforms in the CI stack would render it slightly differently... and it's a doozy! 😄

FreeType's autofitter can use HarfBuzz to work out which glyphs a script covers, so it can derive blue zones for scripts such as Arabic.

Only the Windows wheels were built that way. macOS passed --with-harfbuzz=no (so no HarfBuzz interop, no way, no how), and Linux left it unconfigured, which meant FreeType defaulted to dlopening HarfBuzz at runtime with the name "libharfbuzz.so.0".

However, auditwheel renames shared libraries to avoid conflicts, so the Linux wheels ended up with a FreeType that would only find HarfBuzz if the system had its own copy with the name "libharfbuzz.so.0".


In the current 12.3 wheel off PyPI, the interop symbols do not exist:

$ wget https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl
$ unzip pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl PIL/.dylibs/libfreetype.6.dylib
$ nm -u PIL/.dylibs/libfreetype.6.dylib | grep _hb_ot_
$

but in a wheel built by CI off this branch (see akx#18):

$ unzip 'dist-macOS arm64.zip'
Archive:  dist-macOS arm64.zip
  inflating: pillow-13.0.0.dev0-cp311-cp311-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp312-cp312-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp313-cp313-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp314-cp314-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp314-cp314t-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp315-cp315-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-cp315-cp315t-macosx_11_0_arm64.whl
  inflating: pillow-13.0.0.dev0-pp311-pypy311_pp73-macosx_11_0_arm64.whl
$ unzip pillow-13.0.0.dev0-cp314-cp314-macosx_11_0_arm64.whl PIL/.dylibs/libfreetype.6.dylib
$ nm -u PIL/.dylibs/libfreetype.6.dylib | grep _hb_ot_
_hb_ot_layout_collect_lookups
_hb_ot_layout_lookup_collect_glyphs
_hb_ot_layout_lookup_would_substitute
_hb_ot_tags_from_script_and_language
$

FreeType's autofitter can use HarfBuzz to work out which glyphs
a script covers, so it can derive blue zones for scripts such as Arabic.

Only the Windows wheels were built to allow that.
macOS passed --with-harfbuzz=no, and Linux left it unconfigured,
which meant FreeType defaulted to `dlopen`ing HarfBuzz at runtime
with the name "libharfbuzz.so.0".

`auditwheel`, part of the wheeling process, renames shared libraries
to avoid conflicts, so the Linux wheels ended up with a FreeType that
would only find HarfBuzz if the system had its own copy with the name
"libharfbuzz.so.0"; the bundled copy was never used.
@akx

This comment was marked as outdated.

fi
# FreeType and HarfBuzz each want the other:
# HarfBuzz reads font data through FreeType, and FreeType's autofitter asks HarfBuzz which glyphs a script covers.
# Break the cycle by building FreeType twice, so that the FreeType we ship is linked against the HarfBuzz we ship.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unusual that this should be required, right? Have FreeType or Harfbuzz discussed this anywhere?

@akx akx Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google search autocomplete (on a different device, on mobile right now) suggests https://www.google.com/search?q=freetype+harfbuzz+circular+dependency if you start typing "freetype harf", so that suggests it's a known thing.

Here's a recent-ish Debian bug about it https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1124239

Freetype notes the circular dependency in the release announcement for 2.14.0 on https://freetype.org/

@radarhere radarhere Aug 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conclusion of the Debian bug and the FreeType release notes that the solution is to load HarfBuzz dynamically. Are you going to tell me that option isn't available to us because of auditwheel?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in the PR description, FT does attempt to load HarfBuzz dynamically on main already, but yes - auditwheel renames the bundled dynamic library and fixes up import tables but can't fix the dynamic dlopen call within FT.

The dlopen name is specified like https://github.com/freetype/freetype/blob/master/src/autofit/ft-hb.c#L34 so we'd need to know the name auditwheel will rename harfbuzz to before we compile FT to pass that preprocessor constant.

@radarhere

Copy link
Copy Markdown
Member

@khaledhosny because you're obviously thinking about this sort of thing at the moment, did you have any thoughts on this approach?

@khaledhosny

Copy link
Copy Markdown

@khaledhosny because you're obviously thinking about this sort of thing at the moment, did you have any thoughts on this approach?

This is the usual solution to this issue; build freetype without harfbuzz support, then build harfbuzz with freetype support, then build freetype again with harfbuzz support.

The new way is to let freetype load harfbuzz at runtime which is discussed above.

You can break this circular dependency by either not using FreeType’s font functions in HarfBuzz and use HarfBuzz’s internal font functions, but if you are using horizontal hinting in FreeType you might get different glyph advance widths. Or by not using HarfBuzz in FreeType but the auto hinter will give different results which I think this is what this issue is trying to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants